package com.kexio.file.service.impl;

import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kexio.common.exception.BusinessException;
import com.kexio.file.config.FileStorageProperties;
import com.kexio.file.entity.FileInfo;
import com.kexio.file.entity.StorageObject;
import com.kexio.file.mapper.FileInfoMapper;
import com.kexio.file.service.FileInfoService;
import com.kexio.file.service.StorageObjectService;
import com.kexio.file.storage.StorageManager;
import com.kexio.file.util.FileHashUtils;
import com.kexio.file.vo.FileInfoVO;

/**
 * 文件信息服务实现类
 * 
 * @author Kexio Team
 * @since 1.0.0
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class FileInfoServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> implements FileInfoService {
    
    private static final Logger log = LoggerFactory.getLogger(FileInfoServiceImpl.class);
    
    @Autowired
    private FileInfoMapper fileInfoMapper;
    
    @Autowired
    private StorageManager storageManager;
    
    @Autowired
    private StorageObjectService storageObjectService;
    
    @Autowired
    private FileStorageProperties storageProperties;
    
    // ==================== 文件上传 ====================
    
    @Override
    public FileInfo uploadFile(MultipartFile file, String businessType, String businessKey, 
                               String description, Boolean isPublic, LocalDateTime expireTime) {
        if (file == null || file.isEmpty()) {
            throw new BusinessException("文件不能为空");
        }
        
        try {
            try (InputStream in = file.getInputStream()) {
                return uploadFile(in, file.getOriginalFilename(), 
                                file.getContentType(), file.getSize(), businessType, 
                                businessKey, description, isPublic, expireTime);
            }
        } catch (BusinessException e) {
            // BusinessException 直接重新抛出，避免重复打印
            throw e;
        } catch (Exception e) {
            log.error("文件上传失败: {}", e.getMessage(), e);
            throw new BusinessException("文件上传失败: " + e.getMessage());
        }
    }
    
    @Override
    public FileInfo uploadFile(InputStream inputStream, String originalName, String contentType, 
                               Long fileSize, String businessType, String businessKey, 
                               String description, Boolean isPublic, LocalDateTime expireTime) {
        try {
            // 1. 验证上传限制
            validateUploadLimits(originalName, contentType, fileSize);
            
            // 2. 先将输入流读取到字节数组，避免流重置问题
            byte[] fileBytes;
            try {
                fileBytes = inputStream.readAllBytes();
            } catch (IOException e) {
                throw new BusinessException("读取文件内容失败: " + e.getMessage());
            }
            
            // 2.1 服务端内容探测与白名单校验
            try (java.io.ByteArrayInputStream mimeStream = new java.io.ByteArrayInputStream(fileBytes);
                 java.io.BufferedInputStream bufferedMimeStream = new java.io.BufferedInputStream(mimeStream)) {
                String sniffedMime = sniffMimeType(bufferedMimeStream);
                String resolvedContentType = (StringUtils.hasText(contentType) ? contentType : sniffedMime);
                if (StringUtils.hasText(sniffedMime) && StringUtils.hasText(resolvedContentType)) {
                    // 若前端声明的类型与探测差异过大，可按需强制使用探测结果
                    if (!resolvedContentType.startsWith(sniffedMime.split("/\\*")[0])) {
                        resolvedContentType = sniffedMime;
                    }
                }
                // 基于白名单的再次校验
                validateMimeAllowed(resolvedContentType);

                // 2.2 计算哈希
                FileHashUtils.FileHashResult hashResult;
                try (java.io.ByteArrayInputStream hashStream = new java.io.ByteArrayInputStream(fileBytes)) {
                    hashResult = FileHashUtils.calculateHash(hashStream);
                }
                
                // 3. 检查是否存在重复文件（去重）
                FileInfo existingFile = findDuplicateFile(hashResult.getMd5(), hashResult.getSha256());
                if (existingFile != null) {
                    log.info("发现重复文件，复用存储对象: existingFileId={}, hash={}", 
                            existingFile.getId(), hashResult.getSha256());
                    return createFileInfoFromExisting(existingFile, originalName, businessType, 
                                                    businessKey, description, isPublic, expireTime);
                }
                
                // 4. 创建或获取存储对象  
                StorageObject storageObject;
                try (java.io.ByteArrayInputStream storageStream = new java.io.ByteArrayInputStream(fileBytes)) {
                    storageObject = storageObjectService.createOrGetStorageObject(
                            storageStream, hashResult.getFileSize(), resolvedContentType, null, null, null);
                }
                
                // 5. 创建文件信息记录
                FileInfo fileInfo = new FileInfo();
                fileInfo.setStorageObjectId(storageObject.getId());
                fileInfo.setOriginalName(originalName);
                fileInfo.setDisplayName(originalName);
                fileInfo.setMimeType(resolvedContentType);
                fileInfo.setFileSize(hashResult.getFileSize());
                fileInfo.setContentMd5(hashResult.getMd5());
                fileInfo.setContentSha256(hashResult.getSha256());
                fileInfo.setBusinessType(businessType);
                fileInfo.setBusinessKey(businessKey);
                fileInfo.setDescription(description);
                fileInfo.setIsPublic(isPublic != null ? isPublic : false);
                fileInfo.setExpireTime(expireTime);
                fileInfo.setStatus(1); // 正常状态
                fileInfo.setUploadTime(LocalDateTime.now());
                
                // 提取文件扩展名
                if (StringUtils.hasText(originalName) && originalName.contains(".")) {
                    String extension = originalName.substring(originalName.lastIndexOf(".") + 1);
                    fileInfo.setFileExtension(extension.toLowerCase());
                }
                
                // 设置上传者信息（从安全上下文获取，可选，因为BaseFieldsMetaObjectHandler会自动填充）
                setUploaderInfo(fileInfo);
                
                // 保存到数据库 - BaseFieldsMetaObjectHandler会自动填充create_by, create_time, tenant_id等字段
                save(fileInfo);
                
                log.info("文件上传成功: fileId={}, storageObjectId={}, originalName={}, size={}, hash={}", 
                        fileInfo.getId(), storageObject.getId(), originalName, hashResult.getFileSize(), hashResult.getSha256());
                
                return fileInfo;
            }
            
        } catch (Exception e) {
            log.error("文件上传失败: originalName={}, businessType={}", originalName, businessType, e);
            throw new BusinessException("文件上传失败: " + e.getMessage());
        }
    }
    
    @Override
    public List<FileInfo> uploadFiles(List<MultipartFile> files, String businessType, 
                                      String businessKey, Boolean isPublic) {
        List<FileInfo> result = new ArrayList<>();
        for (MultipartFile file : files) {
            FileInfo fileInfo = uploadFile(file, businessType, businessKey, null, isPublic, null);
            result.add(fileInfo);
        }
        return result;
    }
    
    // ==================== 文件下载 ====================
    
    @Override
    public InputStream downloadFile(String fileId, boolean updateAccessStats) {
        FileInfo fileInfo = getById(fileId);
        if (fileInfo == null) {
            throw new BusinessException("文件不存在");
        }
        
        if (!fileInfo.isValid()) {
            throw new BusinessException("文件已过期或不可用");
        }
        
        // 获取存储对象
        StorageObject storageObject = storageObjectService.getById(fileInfo.getStorageObjectId());
        if (storageObject == null) {
            throw new BusinessException("存储对象不存在");
        }
        
        // 从存储后端获取文件流
        InputStream inputStream = storageManager.getFile(
                storageObject.getStorageType(),
                storageObject.getBucketName(),
                storageObject.getStoragePath()
        );
        
        if (inputStream == null) {
            throw new BusinessException("文件读取失败");
        }
        
        // 更新访问统计
        if (updateAccessStats) {
            updateAccessStats(fileId, true, true);
        }
        
        return inputStream;
    }
    
    @Override
    public String getPreSignedDownloadUrl(String fileId, int expireMinutes) {
        FileInfo fileInfo = getById(fileId);
        if (fileInfo == null) {
            throw new BusinessException("文件不存在");
        }
        
        if (!fileInfo.isValid()) {
            throw new BusinessException("文件已过期或不可用");
        }
        
        // 获取存储对象
        StorageObject storageObject = storageObjectService.getById(fileInfo.getStorageObjectId());
        if (storageObject == null) {
            throw new BusinessException("存储对象不存在");
        }
        
        // 生成预签名下载URL
        String preSignedUrl = storageManager.generatePreSignedDownloadUrl(
                storageObject.getStorageType(),
                storageObject.getBucketName(),
                storageObject.getStoragePath(),
                expireMinutes
        );
        
        // 如果预签名URL为null（本地存储不支持），则返回直接下载URL
        if (preSignedUrl == null) {
            preSignedUrl = "/api/file/fileinfo/download/" + fileId;
            log.info("本地存储不支持预签名URL，返回直接下载URL: {}", preSignedUrl);
        }
        
        // 更新访问统计（仅访问次数，不计下载次数）
        updateAccessStats(fileId, true, false);
        
        return preSignedUrl;
    }
    
    // ==================== 基础CRUD操作 ====================
    
    @Override
    public FileInfo getById(String fileId) {
        return super.getById(fileId);
    }
    
    @Override
    public FileInfoVO getVOById(String fileId) {
        FileInfo fileInfo = getById(fileId);
        if (fileInfo == null) {
            return null;
        }
        
        return convertToVO(fileInfo);
    }
    
    @Override
    public FileInfo updateFileInfo(String fileId, FileInfo fileInfo) {
        FileInfo existing = getById(fileId);
        if (existing == null) {
            throw new BusinessException("文件不存在");
        }
        
        // 只允许更新部分字段
        existing.setDisplayName(fileInfo.getDisplayName());
        existing.setDescription(fileInfo.getDescription());
        existing.setTags(fileInfo.getTags());
        existing.setIsPublic(fileInfo.getIsPublic());
        existing.setExpireTime(fileInfo.getExpireTime());
        
        updateById(existing);
        return existing;
    }
    
    @Override
    public boolean deleteFile(String fileId) {
        FileInfo fileInfo = getById(fileId);
        if (fileInfo == null) {
            return false;
        }
        
        String storageObjectId = fileInfo.getStorageObjectId();
        // 逻辑删除
        boolean result = removeById(fileId);
        
        if (result && StringUtils.hasText(storageObjectId)) {
            try {
                int newRef = storageObjectService.decrementReferenceCount(storageObjectId, 1);
                if (newRef <= 0) {
                    storageObjectService.deleteStorageObject(storageObjectId);
                }
            } catch (Exception ex) {
                log.warn("更新存储对象引用计数失败: storageObjectId={}, fileId={}", storageObjectId, fileId, ex);
            }
        }
        
        return result;
    }
    
    @Override
    public int batchDeleteFiles(List<String> fileIds) {
        if (fileIds == null || fileIds.isEmpty()) {
            return 0;
        }
        
        // 查询详情用于引用计数聚合
        List<FileInfo> files = listByIds(fileIds);
        Map<String, Long> objCount = files.stream()
                .filter(f -> StringUtils.hasText(f.getStorageObjectId()))
                .collect(java.util.stream.Collectors.groupingBy(FileInfo::getStorageObjectId, java.util.stream.Collectors.counting()));
        
        // 批量逻辑删除
        boolean result = removeByIds(fileIds);
        
        if (result) {
            objCount.forEach((objId, cnt) -> {
                try {
                    int newRef = storageObjectService.decrementReferenceCount(objId, cnt.intValue());
                    if (newRef <= 0) {
                        storageObjectService.deleteStorageObject(objId);
                    }
                } catch (Exception ex) {
                    log.warn("批量更新存储对象引用计数失败: storageObjectId={}", objId, ex);
                }
            });
        }
        
        return result ? fileIds.size() : 0;
    }
    
    // ==================== 文件查询 ====================
    
    @Override
    public IPage<FileInfoVO> listFiles(long current, long size, String keyword, String businessType, 
                                       String uploaderId, Integer status) {
        Page<FileInfo> page = new Page<>(current, size);
        
        LambdaQueryWrapper<FileInfo> wrapper = new LambdaQueryWrapper<>();
        
        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w.like(FileInfo::getOriginalName, keyword)
                           .or().like(FileInfo::getDisplayName, keyword)
                           .or().like(FileInfo::getDescription, keyword));
        }
        
        if (StringUtils.hasText(businessType)) {
            wrapper.eq(FileInfo::getBusinessType, businessType);
        }
        
        if (StringUtils.hasText(uploaderId)) {
            wrapper.eq(FileInfo::getUploaderId, uploaderId);
        }
        
        if (status != null) {
            wrapper.eq(FileInfo::getStatus, status);
        }
        
        wrapper.orderByDesc(FileInfo::getCreateTime);
        
        IPage<FileInfo> result = page(page, wrapper);
        
        // 转换为VO
        Page<FileInfoVO> voPage = new Page<>(current, size, result.getTotal());
        List<FileInfoVO> voList = result.getRecords().stream()
                .map(this::convertToVO)
                .collect(java.util.stream.Collectors.toList());
        voPage.setRecords(voList);
        
        return voPage;
    }
    
    @Override
    public List<FileInfo> listByBusinessKey(String businessKey) {
        return fileInfoMapper.selectByBusinessKey(businessKey, null);
    }
    
    @Override
    public IPage<FileInfoVO> listByBusinessType(String businessType, long current, long size) {
        return listFiles(current, size, null, businessType, null, null);
    }
    
    @Override
    public IPage<FileInfoVO> listByUploader(String uploaderId, long current, long size) {
        return listFiles(current, size, null, null, uploaderId, null);
    }
    
    @Override
    public IPage<FileInfoVO> listByMimeType(String mimeTypePattern, long current, long size) {
        Page<FileInfo> page = new Page<>(current, size);
        IPage<FileInfo> result = fileInfoMapper.selectByMimeType(mimeTypePattern, null, page);
        
        // 转换为VO
        Page<FileInfoVO> voPage = new Page<>(current, size, result.getTotal());
        List<FileInfoVO> voList = result.getRecords().stream()
                .map(this::convertToVO)
                .collect(java.util.stream.Collectors.toList());
        voPage.setRecords(voList);
        
        return voPage;
    }
    
    // ==================== 文件统计 ====================
    
    @Override
    public Long countByUploader(String uploaderId) {
        return fileInfoMapper.countByUploader(uploaderId, null);
    }
    
    @Override
    public Long sumFileSizeByUploader(String uploaderId) {
        return fileInfoMapper.sumFileSizeByUploader(uploaderId, null);
    }
    
    @Override
    public Long countByBusinessType(String businessType) {
        return fileInfoMapper.countByBusinessType(businessType, null);
    }
    
    @Override
    public Map<String, Object> getFileStats(String uploaderId) {
        Map<String, Object> stats = new HashMap<>();
        
        if (StringUtils.hasText(uploaderId)) {
            stats.put("totalCount", countByUploader(uploaderId));
            stats.put("totalSize", sumFileSizeByUploader(uploaderId));
        } else {
            // 全局统计
            stats.put("totalCount", count());
            // TODO: 添加更多统计信息
        }
        
        return stats;
    }
    
    // ==================== 文件维护 ====================
    
    @Override
    public int cleanupExpiredFiles(int batchSize) {
        List<FileInfo> expiredFiles = fileInfoMapper.selectExpiredFiles(LocalDateTime.now(), null, batchSize);
        if (expiredFiles.isEmpty()) {
            return 0;
        }
        
        List<String> fileIds = expiredFiles.stream()
                .map(FileInfo::getId)
                .collect(java.util.stream.Collectors.toList());
        
        return batchDeleteFiles(fileIds);
    }
    
    @Override
    public int cleanupUnusedFiles(int days, int batchSize) {
        LocalDateTime cutoffTime = LocalDateTime.now().minusDays(days);
        List<FileInfo> unusedFiles = fileInfoMapper.selectUnusedFiles(cutoffTime, null, batchSize);
        
        if (unusedFiles.isEmpty()) {
            return 0;
        }
        
        List<String> fileIds = unusedFiles.stream()
                .map(FileInfo::getId)
                .collect(java.util.stream.Collectors.toList());
        
        return batchDeleteFiles(fileIds);
    }
    
    @Override
    public void updateAccessStats(String fileId, boolean incrementAccess, boolean incrementDownload) {
        fileInfoMapper.updateAccessStats(fileId, LocalDateTime.now(), incrementAccess, incrementDownload);
    }
    
    @Override
    public int batchUpdateStatus(List<String> fileIds, Integer status) {
        return fileInfoMapper.batchUpdateStatus(fileIds, status, "system");
    }
    
    // ==================== 高级功能 ====================
    
    @Override
    public FileInfo copyFile(String sourceFileId, String newBusinessType, String newBusinessKey, String newDescription) {
        FileInfo sourceFile = getById(sourceFileId);
        if (sourceFile == null) {
            throw new BusinessException("源文件不存在");
        }
        
        FileInfo newFile = new FileInfo();
        // 复制基本信息
        newFile.setStorageObjectId(sourceFile.getStorageObjectId());
        newFile.setOriginalName(sourceFile.getOriginalName());
        newFile.setDisplayName(sourceFile.getDisplayName());
        newFile.setFileExtension(sourceFile.getFileExtension());
        newFile.setMimeType(sourceFile.getMimeType());
        newFile.setFileSize(sourceFile.getFileSize());
        newFile.setContentMd5(sourceFile.getContentMd5());
        newFile.setContentSha256(sourceFile.getContentSha256());
        
        // 设置新的业务信息
        newFile.setBusinessType(newBusinessType);
        newFile.setBusinessKey(newBusinessKey);
        newFile.setDescription(newDescription);
        newFile.setIsPublic(sourceFile.getIsPublic());
        newFile.setStatus(1);
        newFile.setUploadTime(LocalDateTime.now());
        
        save(newFile);
        
        // 增加存储对象引用计数
        storageObjectService.incrementReferenceCount(sourceFile.getStorageObjectId(), 1);
        
        return newFile;
    }
    
    @Override
    public FileInfo renameFile(String fileId, String newDisplayName) {
        FileInfo fileInfo = getById(fileId);
        if (fileInfo == null) {
            throw new BusinessException("文件不存在");
        }
        
        fileInfo.setDisplayName(newDisplayName);
        updateById(fileInfo);
        
        return fileInfo;
    }
    
    @Override
    public FileInfo setFileTags(String fileId, String tags) {
        FileInfo fileInfo = getById(fileId);
        if (fileInfo == null) {
            throw new BusinessException("文件不存在");
        }
        
        fileInfo.setTags(tags);
        updateById(fileInfo);
        
        return fileInfo;
    }
    
    @Override
    public FileInfo findDuplicateFile(String contentMd5, String contentSha256) {
        return fileInfoMapper.selectByContentHash(contentMd5, contentSha256, null);
    }
    
    // ==================== 私有方法 ====================
    
    /**
     * 转换为VO对象
     */
    private FileInfoVO convertToVO(FileInfo fileInfo) {
        FileInfoVO vo = new FileInfoVO();
        vo.setId(fileInfo.getId());
        vo.setStorageObjectId(fileInfo.getStorageObjectId());
        vo.setOriginalName(fileInfo.getOriginalName());
        vo.setDisplayName(fileInfo.getDisplayName());
        vo.setFileExtension(fileInfo.getFileExtension());
        vo.setMimeType(fileInfo.getMimeType());
        vo.setFileSize(fileInfo.getFileSize());
        vo.setHumanReadableSize(fileInfo.getHumanReadableSize());
        vo.setContentMd5(fileInfo.getContentMd5());
        vo.setBusinessKey(fileInfo.getBusinessKey());
        vo.setBusinessType(fileInfo.getBusinessType());
        vo.setTags(fileInfo.getTags());
        vo.setStatus(fileInfo.getStatus());
        vo.setStatusText(getStatusText(fileInfo.getStatus()));
        vo.setIsPublic(fileInfo.getIsPublic());
        vo.setUploaderId(fileInfo.getUploaderId());
        vo.setUploaderName(fileInfo.getUploaderName());
        vo.setUploadIp(fileInfo.getUploadIp());
        vo.setUploadTime(fileInfo.getUploadTime());
        vo.setLastAccessTime(fileInfo.getLastAccessTime());
        vo.setAccessCount(fileInfo.getAccessCount());
        vo.setDownloadCount(fileInfo.getDownloadCount());
        vo.setExpireTime(fileInfo.getExpireTime());
        vo.setDescription(fileInfo.getDescription());
        vo.setFileType(getFileType(fileInfo.getMimeType()));
        vo.setExpired(fileInfo.isExpired());
        vo.setCreateTime(fileInfo.getCreateTime());
        vo.setUpdateTime(fileInfo.getUpdateTime());
        
        return vo;
    }
    
    /**
     * 获取状态文本
     */
    private String getStatusText(Integer status) {
        if (status == null) return "未知";
        switch (status) {
            case 0: return "上传中";
            case 1: return "正常";
            case 2: return "处理中";
            case 3: return "已损坏";
            case 4: return "已删除";
            default: return "未知";
        }
    }
    
    /**
     * 获取文件类型
     */
    private String getFileType(String mimeType) {
        if (mimeType == null) return "other";
        if (mimeType.startsWith("image/")) return "image";
        if (mimeType.startsWith("video/")) return "video";
        if (mimeType.startsWith("audio/")) return "audio";
        if (mimeType.startsWith("application/pdf") || 
            mimeType.startsWith("application/msword") ||
            mimeType.startsWith("application/vnd.openxmlformats-officedocument") ||
            mimeType.startsWith("text/")) return "document";
        return "other";
    }
    
    /**
     * 验证上传限制
     */
    private void validateUploadLimits(String originalName, String contentType, Long fileSize) {
        FileStorageProperties.UploadLimits limits = storageProperties.getUpload();
        
        // 检查文件大小
        if (fileSize != null && fileSize > limits.getMaxFileSize()) {
            throw new BusinessException("文件大小超过限制: " + fileSize + " > " + limits.getMaxFileSize());
        }
        
        // 检查文件扩展名
        if (StringUtils.hasText(originalName) && originalName.contains(".")) {
            String extension = originalName.substring(originalName.lastIndexOf(".") + 1).toLowerCase();
            String[] blockedExtensions = limits.getBlockedExtensions();
            if (blockedExtensions != null) {
                for (String blocked : blockedExtensions) {
                    if (blocked.equalsIgnoreCase(extension)) {
                        throw new BusinessException("不允许的文件类型: " + extension);
                    }
                }
            }
        }
        
        // 检查MIME类型
        if (StringUtils.hasText(contentType)) {
            String[] allowedMimeTypes = limits.getAllowedMimeTypes();
            if (allowedMimeTypes != null && allowedMimeTypes.length > 0) {
                boolean allowed = false;
                for (String allowedType : allowedMimeTypes) {
                    if (allowedType.endsWith("*")) {
                        String prefix = allowedType.substring(0, allowedType.length() - 1);
                        if (contentType.startsWith(prefix)) {
                            allowed = true;
                            break;
                        }
                    } else if (allowedType.equals(contentType)) {
                        allowed = true;
                        break;
                    }
                }
                if (!allowed) {
                    throw new BusinessException("不允许的文件类型: " + contentType);
                }
            }
        }
    }

    /**
     * 简单 MIME 探测：使用 mark/reset 对输入流进行少量读取后探测，再复位。
     */
    private String sniffMimeType(java.io.BufferedInputStream in) {
        try {
            in.mark(8192);
            // 读取头部字节到临时文件或缓冲区进行 Files.probeContentType 需要 Path，这里退而求其次：
            // 如果需要更准确的识别，建议引入 Apache Tika。
            // 先尝试通过 URLConnection.guessContentTypeFromStream
            String mime = java.net.URLConnection.guessContentTypeFromStream(in);
            in.reset();
            return mime;
        } catch (Exception e) {
            try { in.reset(); } catch (Exception ignore) {}
            return null;
        }
    }

    private void validateMimeAllowed(String contentType) {
        if (!StringUtils.hasText(contentType)) return;
        String[] allowed = storageProperties.getUpload().getAllowedMimeTypes();
        if (allowed == null || allowed.length == 0) return;
        boolean ok = false;
        for (String pattern : allowed) {
            if (pattern.endsWith("*")) {
                String prefix = pattern.substring(0, pattern.length() - 1);
                if (contentType.startsWith(prefix)) { ok = true; break; }
            } else if (pattern.equals(contentType)) {
                ok = true; break;
            }
        }
        if (!ok) {
            throw new BusinessException("不允许的文件类型: " + contentType);
        }
    }
    
    /**
     * 设置上传者信息
     */
    private void setUploaderInfo(FileInfo fileInfo) {
        // TODO: 从安全上下文获取当前用户信息
        // ✅ 使用AuthContextUtils.getCurrentUser() 获取用户信息
        fileInfo.setUploaderId("system"); // 临时设置
        fileInfo.setUploaderName("系统");
        
        // TODO: 获取客户端IP
        fileInfo.setUploadIp("127.0.0.1");
    }
    
    /**
     * 从现有文件创建新的文件信息（去重场景）
     */
    private FileInfo createFileInfoFromExisting(FileInfo existingFile, String originalName, 
                                               String businessType, String businessKey, 
                                               String description, Boolean isPublic, 
                                               LocalDateTime expireTime) {
        // 增加存储对象引用计数
        storageObjectService.incrementReferenceCount(existingFile.getStorageObjectId(), 1);
        
        // 创建新的文件信息记录
        FileInfo newFileInfo = new FileInfo();
        newFileInfo.setStorageObjectId(existingFile.getStorageObjectId());
        newFileInfo.setOriginalName(originalName);
        newFileInfo.setDisplayName(originalName);
        newFileInfo.setFileExtension(existingFile.getFileExtension());
        newFileInfo.setMimeType(existingFile.getMimeType());
        newFileInfo.setFileSize(existingFile.getFileSize());
        newFileInfo.setContentMd5(existingFile.getContentMd5());
        newFileInfo.setContentSha256(existingFile.getContentSha256());
        newFileInfo.setBusinessType(businessType);
        newFileInfo.setBusinessKey(businessKey);
        newFileInfo.setDescription(description);
        newFileInfo.setIsPublic(isPublic != null ? isPublic : false);
        newFileInfo.setExpireTime(expireTime);
        newFileInfo.setStatus(1);
        newFileInfo.setUploadTime(LocalDateTime.now());
        
        // 设置上传者信息
        setUploaderInfo(newFileInfo);
        
        // 保存到数据库
        save(newFileInfo);
        
        return newFileInfo;
    }
}
